﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Runtime.InteropServices.ComTypes;
using DShowNET;
using DShowNET.Device;

namespace DFControlLibrary
{
    public delegate void CaptureDone(object sender, WebcamEventArgs e);

    /// <summary>
    /// キャプチャイベント引数クラス
    /// </summary>
    public class WebcamEventArgs : System.EventArgs
    {
        private System.Drawing.Image m_Image;
        private ulong m_FrameNumber = 0;

        public WebcamEventArgs()
        {
        }

        /// <summary>
        ///  WebCamImage
        ///  キャプチャした画像
        /// </summary>
        public System.Drawing.Image WebCamImage
        {
            get
            { return m_Image; }

            set
            { m_Image = value; }
        }

        /// <summary>
        /// FrameNumber
        /// フレーム番号
        /// </summary>
        public ulong FrameNumber
        {
            get
            { return m_FrameNumber; }

            set
            { m_FrameNumber = value; }
        }
    }

    /// <summary>
    /// カメラ制御クラス
    /// </summary>
    class DXCapture : ISampleGrabberCB
    {
        private bool active = false;		//初期化正常完了を示すフラグ
		private bool capturedFlag = true;	//キャプチャの終了を示すフラグ
		private bool updatedFlag = false;   //ビットマップの更新を示すフラグ

		private ArrayList captureDevices;//カメラデバイスの一覧．

		private IBaseFilter captureFilter; //ソースフィルタ

		private IGraphBuilder graphBuilder; //基本的なフィルタグラフマネージャ

		private IMediaControl mediaControl;//データのストリーミングの移動、ポーズ、停止などの処理用のインタフェース．
		private IMediaEventEx mediaEvent; //DirectShowイベント処理用のインタフェース
		
		private ICaptureGraphBuilder2 captureGraphBuilder;//ビデオキャプチャ＆編集用のメソッドを備えたキャプチャグラフビルダ
		private ISampleGrabber sampleGrabber; //フィルタグラフ内を通る個々のデータ取得用のインタフェース． 
		private IBaseFilter grabFilter; //Grabber Filterのインタフェース．

		private VideoInfoHeader videoInfoHeader = null;//ビデオイメージのフォーマットを記述する構造体
		

		private byte[] frameArray;//キャプチャしたフレームデータ用の配列	

        // event delegate
        public delegate void WebCamEventHandler(object source, WebcamEventArgs e);
        // fired when a new image is captured
        public event WebCamEventHandler ImageCaptured;

        // イベント引数
        private WebcamEventArgs x = new WebcamEventArgs();

        // フレーム番号
        private ulong FrameNumber = 0;

		Panel videoPanel;//レンダリングしたビデオを画面表示する際のパネル		
		int videoNum; //何番目のUSBキャプチャデバイスかを指定

        /// <summary>
        /// キャプチャした画像の高さ
        /// </summary>
        public int CaptureHeight
        {
            get
            {
                if (videoInfoHeader == null)
                    return 240;
                else
                    return videoInfoHeader.BmiHeader.Height;
            }
        }

        /// <summary>
        /// キャプチャした画像の幅
        /// </summary>
        public int CaptureWidth
        {
            get
            {
                if (videoInfoHeader == null)
                    return 320;
                else
                    return videoInfoHeader.BmiHeader.Width; }
        }

        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="panel">レンダリングしたビデオを画面表示する際のパネル</param>
        /// <param name="num">何番目のUSBキャプチャデバイスかを指定</param>
		public DXCapture(Panel panel, int num)
		{
            videoPanel = panel;
			this.active = false;
			this.videoNum = num;

			//DirectX ver 8.1以上が正常にインストールされているか？
			if(! DsUtils.IsCorrectDirectXVersion())
			{
				//MessageBox.Show("アプリケーションの動作にはDirectX ver 8.1以上が必要です．" );
				return;
			}

			//PCに接続されているキャプチャデバイス（USBカメラなど)のリストを取得．
			if(!DsDev.GetDevicesOfCat(FilterCategory.VideoInputDevice, out captureDevices))
			{
				//MessageBox.Show("ビデオキャプチャ可能なデバイスが見つかりません．");
				return;
			}

		}

        ~DXCapture()
		{			
			CloseInterfaces();
		}

        /// <summary>
        /// DicrectShow関連の初期化
        /// </summary>
        /// <returns>成否</returns>
		public bool InitVideo()
		{
			int result;

			try
            {
			    DsDevice device = null;
			    //指定されたデバイスを選択
			    device = captureDevices[this.videoNum] as DsDevice; 
			
			    //キャプチャ用デバイスが選ばれたか？
			    if(device == null)
                    return false;			    

				//ビデオキャプチャデバイスをフィルタのインスタンスに対応させる．
                if (!CreateCaptureDevice(device.Mon))
					return false;

				//各種COM コンポーネントを呼び出し，インタフェースを取得する．
				if(!GetInterfaces()) 
					return false;
				
				//画像のプレビューや静止画キャプチャ用の設定を行う．
				if(!SetupGraph())	
					return false;

				//プレビューを開始する．
				result = mediaControl.Run();

				if(result < 0)
					Marshal.ThrowExceptionForHR(result);

                //初期設定は成功．
                this.active = true;

				return true;
			}
			catch
			{
				return false;
			}
		}

        /// <summary>
        /// ビデオキャプチャデバイスをソースフィルタのインスタンスに対応させる．
        /// </summary>
        /// <returns>成否</returns>
		bool CreateCaptureDevice(IMoniker moniker)
		{
			object captureObject = null;
			
			try
			{
				//キャプチャデバイス(device)とソースフィルタ(captureFilter)を対応付ける．
				Guid guidBF = typeof(IBaseFilter).GUID;
				moniker.BindToObject(null, null, ref guidBF, out captureObject);
				captureFilter = (IBaseFilter) captureObject;
				captureObject = null;

				return true;
			}
			catch
			{
				return false;
			}
			finally
			{
				if(captureObject != null)
				{
					Marshal.ReleaseComObject(captureObject);
					captureObject = null;
				}
			}
		}

        /// <summary>
        /// 各種のCOM コンポーネントを作成し，インタフェースを取得する．
        /// </summary>
        /// <returns>成否</returns>
		bool GetInterfaces()
		{
			Type comType = null;
			object comObject = null;

			try
			{	
				//graphBuilderを作成．
				comType = Type.GetTypeFromCLSID(Clsid.FilterGraph);
				if(comType == null)
					throw new NotImplementedException("DirectShowのFiterGraphオブジェクトが作成できません．");
				comObject = Activator.CreateInstance(comType);
				graphBuilder = (IGraphBuilder) comObject;
				comObject = null;

				//キャプチャグラフビルダ(captureGraphBuilder)を作成．
				comType = Type.GetTypeFromCLSID(Clsid.CaptureGraphBuilder2);
				comObject = Activator.CreateInstance(comType);
				captureGraphBuilder = (ICaptureGraphBuilder2) comObject;
				comObject = null;

				//サンプルグラバ(sampleGrabber)を作成
				comType = Type.GetTypeFromCLSID(Clsid.SampleGrabber);
				if(comType == null)
					throw new NotImplementedException("DirectShowのSampleGrabberオブジェクトが作成できません．");
				comObject = Activator.CreateInstance(comType);
				sampleGrabber = (ISampleGrabber) comObject;
				comObject = null;

				//各種インタフェースを取得．
				mediaControl = (IMediaControl) graphBuilder;
				mediaEvent = (IMediaEventEx) graphBuilder;

                //フィルタと関連付ける．
				grabFilter = (IBaseFilter) sampleGrabber;

				return true;
			}
		
			catch
			{
				return false;	
			}

			finally
			{
				//COMリソースを解放
				if(comObject !=null)
				{
					Marshal.ReleaseComObject(comObject);
					comObject = null;
				}
			}
		}

        /// <summary>
        /// 画像のプレビューや静止画キャプチャ用の設定を行う．
        /// </summary>
        /// <returns>成否</returns>
		bool SetupGraph()
		{
			int result;
		
			try
			{
				//captureGraphBuilder（キャプチャグラフビルダ）をgraphBuilder（フィルタグラフマネージャ）に追加．
				result = captureGraphBuilder.SetFiltergraph(graphBuilder);
				if(result < 0) Marshal.ThrowExceptionForHR(result);
 
				//captureFilter(ソースフィルタ)をgraphBuilder（フィルタグラフマネージャ）に追加．
				result = graphBuilder.AddFilter(captureFilter, "Video Capture Device");
				if(result < 0) Marshal.ThrowExceptionForHR(result);
				
				//キャプチャするビデオデータのフォーマットを設定．
				AMMediaType amMediaType = new AMMediaType();
				amMediaType.majorType = MediaType.Video;
                amMediaType.subType = MediaSubType.RGB24;
				amMediaType.formatType = FormatType.VideoInfo; 
				
				result = sampleGrabber.SetMediaType(amMediaType);
				if(result < 0) Marshal.ThrowExceptionForHR(result);
				
				//grabFilter(変換フィルタ)をgraphBuilder（フィルタグラフマネージャ）に追加．
				result = graphBuilder.AddFilter(grabFilter, "Frame Grab Filter");
				if(result < 0) Marshal.ThrowExceptionForHR(result);

				Guid pinCategory;
				Guid mediaType; 

				//キャプチャフィルタをサンプルグラバーフィルタに接続する．
				pinCategory = PinCategory.Capture;
				mediaType = MediaType.Video;
				result = captureGraphBuilder.RenderStream(ref pinCategory, ref mediaType, 
					captureFilter, null, grabFilter);
				if(result < 0) Marshal.ThrowExceptionForHR(result);				
				

				//フレームキャプチャの設定が完了したかを確認する．
				amMediaType = new AMMediaType();
				result = sampleGrabber.GetConnectedMediaType(amMediaType);
				if(result < 0) Marshal.ThrowExceptionForHR(result);
				if((amMediaType.formatType != FormatType.VideoInfo) || (amMediaType.formatPtr == IntPtr.Zero))
					throw new NotSupportedException("キャプチャ(Grab)できないメディアフォーマットです．");

				//キャプチャするビデオデータのフォーマットから，videoInfoHeaderを作成する．
				videoInfoHeader = 
					(VideoInfoHeader) Marshal.PtrToStructure(amMediaType.formatPtr, typeof(VideoInfoHeader));
				Marshal.FreeCoTaskMem(amMediaType.formatPtr);
				amMediaType.formatPtr = IntPtr.Zero;
			
				//フィルタ内を通るサンプルをバッファにコピーしないように指定する．
				result = sampleGrabber.SetBufferSamples(false);
				//サンプルを一つ（1フレーム）受け取ったらフィルタを停止するように指定する．
				if(result == 0) result = sampleGrabber.SetOneShot(false);
				//コールバック関数の利用を停止する．
				if(result == 0) result = sampleGrabber.SetCallback(this, 1);

				if(result < 0) Marshal.ThrowExceptionForHR(result);

				return true;
			}
			catch
			{
				return false;
			}
			
		}

        /// <summary>
        /// 静止画のキャプチャを開始する．
        /// </summary>
        /// <returns>成否</returns>
		public bool StartGrabber()
		{
			if(sampleGrabber == null) return false;

			if(frameArray == null)
			{
				//キャプチャするフレームデータのサイズを設定する．
				int size = videoInfoHeader.BmiHeader.ImageSize;		
				if ((size < 1000) || (size > 16000000)) return false;
				frameArray = new byte [size + 512];
			}

			capturedFlag = false;
            
			return true;
		}

        /// <summary>
        /// 静止画のキャプチャを停止する．
        /// </summary>
        /// <returns>成否</returns>
        public bool StopGrabber()
        {
            int result;
            if (sampleGrabber == null) return false;
            try
            {

                //ビデオデータのサンプリングに利用するコールバック メソッドを指定する．
                //第一引数	 ISampleGrabberCB インターフェイスへのポインタ
                //			 nullを指定するとコールバックを解除
                //第二引数	0->ISampleGrabberCB.SampleCB メソッドを利用 	
                //			1->ISampleGrabberCB.BufferCB メソッドを利用
                result = sampleGrabber.SetCallback(null, 0);
            }
            catch
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// フレームキャプチャ完了時に呼び出されるコールバック関数
        /// </summary>
		int ISampleGrabberCB.SampleCB(double SampleTime, IMediaSample pSample)
		{
			return 0;
		}

        /// <summary>
        /// フレームキャプチャ完了時に呼び出されるコールバック関数
        /// </summary>
		int ISampleGrabberCB.BufferCB(double SampleTime, IntPtr pBuffer, int BufferLength)
		{
			//既にコールバックが呼び出されているか，frameArrayが初期化されていなければ終了．
			if(capturedFlag || (frameArray == null)) 
			{
				return 0;
			}
			
			capturedFlag = true;
			//メモリ内のサンプリングされたデータ(pBuffer)を配列(frameArray)にコピーする．
            if ((pBuffer != IntPtr.Zero) && (BufferLength > 1000) && (BufferLength <= frameArray.Length))
            {
                Marshal.Copy(pBuffer, frameArray, 0, BufferLength);

                try
                {
                    OnCaptureDone();
                }
                catch
                {
                }
            }
			return 0;
		}

        /// <summary>
        /// キャプチャしたデータの配列をメモリ空間内に固定し，ビットマップに変換する．
        /// </summary>
		void OnCaptureDone()
		{
			try
			{
				//フレームデータのサイズを取得
				int width = videoInfoHeader.BmiHeader.Width;
				int height = videoInfoHeader.BmiHeader.Height;

				//widthが4の倍数でない場合＆widthとheightの値が適正でない場合は終了．
				if( ((width & 0x03) != 0) || (width < 32) || (width > 4096) || (height < 32) || (height> 4096) )
					return;

               ////frameArrayを格納したメモリアドレスから，ビットマップデータを作成．
                Bitmap capturedBitmap = new Bitmap(width, height);
                BitmapData bd = capturedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
                Marshal.Copy(frameArray, 0, bd.Scan0, bd.Stride * bd.Height);
                capturedBitmap.UnlockBits(bd);
                capturedBitmap.RotateFlip(RotateFlipType.Rotate180FlipX);

                // paste the frame into the event args image
                if (ImageCaptured != null)
                {
                    /*
                    * For some reason, the API is not resizing the video
                    * feed to the width and height provided when the video
                    * feed was started, so we must resize the image here
                    */
                    x.WebCamImage = capturedBitmap;
                    x.FrameNumber = FrameNumber++;

                    // raise the event
                    this.ImageCaptured(this, x);
                }

				//更新完了．
				updatedFlag = true;

				return;
			}
			catch
			{
			}
		}

        /// <summary>
        /// COMやインタフェースの解放を行う
        /// </summary>
		public void CloseInterfaces()
		{
			try
			{
                capturedFlag = true;
				if(mediaControl != null)
				{
					mediaControl.Stop();
					Thread.Sleep(100);
					mediaControl = null;
				}

				if(grabFilter != null) grabFilter = null;

				if(sampleGrabber != null)
				{
					Marshal.ReleaseComObject(sampleGrabber);
					sampleGrabber = null;
				}

				if(captureGraphBuilder != null)
				{
					Marshal.ReleaseComObject(captureGraphBuilder);
					captureGraphBuilder = null;
				}

				if(graphBuilder != null)
				{
					Marshal.ReleaseComObject(graphBuilder);
					graphBuilder = null;
				}

				if(captureFilter != null)
				{
					captureFilter.Stop();
					Marshal.ReleaseComObject(captureFilter);
					captureFilter = null;
				}

				if(captureDevices != null)
				{
					foreach(DsDevice d in captureDevices)
					{
						d.Dispose();
					}
					captureDevices = null;
				}
			}
			catch
			{
			}
		}

        /// <summary>
        /// 初期化が正常に行われたか？
        /// </summary>
		public bool IsActive
		{
			get {return this.active;}
		}

        /// <summary>
        /// 静止画のキャプチャが終了＆更新したか？
        /// </summary>
		public bool IsUpdated
		{
			get{return this.updatedFlag;}
			set{this.updatedFlag = value;}
		}
    }
}
